package edu.chalmers.gos;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;

import android.content.res.Configuration;
import android.nfc.NfcEvent;
import android.widget.Toast;
import edu.chalmers.gos.Cards.Card;
import edu.chalmers.targine.ARManager;
import edu.chalmers.targine.Arbject;
import edu.chalmers.targine.GameLayer;
import edu.dhbw.andar.exceptions.AndARException;
import edu.dhbw.andar.util.AssetsFileUtil;
import edu.dhbw.andar.util.BaseFileUtil;

public class GameOfStones extends ARManager{
	private HashMap<String, Integer> initiatives = new HashMap<String, Integer>();
	private ArrayList<String> players = new ArrayList<String>();
	private int nextPlayerIndex = 0;
	private CardArbject currentArbject = null;
	private CardArbject resourceArbject = null;
	private Button proceedButton, redButton, greenButton, blueButton;
	private HandInterface handInterface;
	private HandInterface plotInterface;
	private CombatStats combatStats;
	private Text debugText;
	
	private Cards cards = Cards.getInstance();
	private ArrayDeque<Card> localDeck;
	private ArrayList<Card> plotHand;
	private ArrayList<Card> localHand = new ArrayList<Card>();
	private Card currentCard = null;
	private Card currentPlot = null;
	private String currentChallengeType = null;
	private String currentChallenger = null;
	private ArrayList<String> challengingUnits = new ArrayList<String>();
	private ArrayList<String> defendingUnits = new ArrayList<String>();
	private boolean proceed = false;
	
	public static final String SETUP = "setup";
	public static final String PLOT = "plot";
	public static final String DRAW = "draw";
	public static final String PRODUCTION = "production";
	public static final String MARSHALLING = "marshalling";
	public static final String WAITING_MARSHALLING = "waitingMarshalling";
	public static final String CHALLENGES = "challenges";
	public static final String WAITING_CHALLENGE = "waitingChallenge";
	public static final String DEFENDING = "defending";
	public static final String WAITING_DEFENCE = "waitingDefence";
	public static final String RED_DEFEATED = "redDefeated";
	public static final String GREEN_VICTORY = "greenVictory";
	public static final String GREEN_DEFEATED = "greenDefeated";
	public static final String DEFENDED = "defended";
	public static final String DOMINANCE = "dominance";
	public static final String STAND = "stand";
	public static final String TAXATION = "taxation"; //TODO: to put into use
	private String gameState = SETUP;
	
	public static final String JOIN = "join";
	public static final String CARD = "card";
	public static final String PLAYER = "gosPlayer";
	public static final String CONTINUE = "continue";
	public static final String KNEEL = Card.KNEEL;
	public static final String RED = Card.RED;
	public static final String GREEN = Card.GREEN;
	public static final String BLUE = Card.BLUE;
	public static final String DEFENDER_WON = "defenderWon";
	public static final String SEND_CHALLENGERS = "sendChallengers";
	public static final String VP = "victoryPoint";
	public static final String VICTORY = "victory";
	public static final String NEXT_ROUND = "nextRound";
	
	@Override
    public void onStart(){
    	super.onStart();
    	//Game state interface
    	proceedButton = new Button("d-pad.png", 98, 98, CONTINUE);
    	((GameLayer)(cocosScene.getChildren().get(0))).addComponent(proceedButton);
	    //Hand interface
    	handInterface = new HandInterface("card_bd.png", WINDOW_WIDTH / 2, 250);
	    handInterface.loadCards("card_bd.png", Cards.cards);
	    handInterface.getNode().setScale(2.0f);
	    ((GameLayer)(cocosScene.getChildren().get(0))).addComponent(handInterface);
	    //Plot hand interface
	    plotInterface = new HandInterface("card_bd.png", WINDOW_WIDTH / 2, 250);
	    plotInterface.loadCards("card_bd.png", Cards.cards);
	    plotInterface.getNode().setScale(2.0f);
	    ((GameLayer)(cocosScene.getChildren().get(0))).addComponent(plotInterface);
	    //Challenge choice interface
	    redButton = new Button("button_red.png", 3 * 98, 98, RED);
    	((GameLayer)(cocosScene.getChildren().get(0))).addComponent(redButton);
    	greenButton = new Button("button_green.png", 5 * 98, 98, GREEN);
    	((GameLayer)(cocosScene.getChildren().get(0))).addComponent(greenButton);
    	blueButton = new Button("button_blue.png", 7 * 98, 98, BLUE);
    	((GameLayer)(cocosScene.getChildren().get(0))).addComponent(blueButton);
    	//Combat data
    	combatStats = new CombatStats();
    	((GameLayer)(cocosScene.getChildren().get(0))).addComponent(combatStats);
    	//Debug text
    	debugText = new Text(WINDOW_WIDTH / 2, 600, SETUP);
    	((GameLayer)(cocosScene.getChildren().get(0))).addComponent(debugText);
	    //Prepare deck and hand
	    localDeck = new ArrayDeque<Cards.Card>(cards.darkDeck);
	    setPlot(pollPlotCards(localDeck));
//	    shuffleDeck(localDeck);
	    setHand(drawCards(3));
	    //add local mac address to players
	    players.add(LOCAL_MAC);
	    initiatives.put(LOCAL_MAC, -1);
	    //clear cards
	    resetCardArbjects();
    }
	
	@Override
	protected void registerModels(){
		arbjects = new ConcurrentHashMap<String, Arbject>();
		
    	try {    		
    		BaseFileUtil fileUtil = new AssetsFileUtil(getResources().getAssets());
			fileUtil.setBaseFolder("models/");			

//			ObjParser parser = new ObjParser(fileUtil);
			if(fileUtil != null){
//				BufferedReader fileReader;
//				fileReader = fileUtil.getReaderFromName("ski_slope_1_new.obj");
//				Log.d("LOAD_MODEL", "Loading ski_slope_1");
//				models.put("ski_slope_1", parser.parse("ski_slope_1", fileReader));
				
				registerArbject("0c 10 ef 0f", "linda", "pattern1");
				registerArbject("ae e0 41 d2", "oleander", "pattern3");
				registerArbject("5e 6a 42 d2", "pokeylope", "pattern2");
				registerArbject("9e cc 40 d2", "sasha", "pattern4");
				registerArbject("ee 0a 6d b7", "shield", "pattern_shield");
				registerArbject("de cf 3d d2", "lens", "pattern_lens");
				registerArbject("ee 42 39 d2", "harry", "pattern_harry");
//				registerArbject("6c be 02 06 66 24 16 e0", "harry", "pattern_harry");
				registerArbject("ae 67 3f d2", "tetris", "pattern_tetris");
				registerArbject("ce 37 c6 aa", "axe", "pattern_axe");
				registerArbject("fe c3 c7 aa", "castle", "pattern_castle");
				registerArbject("6e b6 6e b7", "sword", "pattern_sword");
				registerArbject("ee aa 3d d2", "moon", "pattern_moon");
			}
		} catch (AndARException e){
			e.printStackTrace();
		}
//    	catch (IOException e) {
//			e.printStackTrace();
//		} catch (ParseException e) {
//			e.printStackTrace();
//		}
    }
	
	@Override
	public void registerArbject(String arbjectID, String patternName, String patternFile) throws AndARException{
		arbjects.put(arbjectID, new CardArbject(patternName, patternFile, arbjectID, "", this));
		serverConnection.sendCommandToServer("currentValue:" + arbjectID + "." + CARD);
		artoolkit.registerARObject(arbjects.get(arbjectID));
    }
	
	/**
	 * GAME LOOP
	 */
	@Override
	public void update(){
		if(gameState.equals(SETUP)){
			handInterface.setVisible(false);
			plotInterface.setVisible(false);
			proceedButton.setVisible(false);
			redButton.setVisible(false);
			greenButton.setVisible(false);
			blueButton.setVisible(false);
			combatStats.setVisible(false);
			if(resourceArbject != null){
				setGameState(PLOT);
			}
		} else if(gameState.equals(PLOT)){
			plotInterface.setVisible(true);
			if(currentPlot != null){
				plotInterface.setVisible(false);
				plotInterface.setSelected(false);
				setGameState(DRAW);
				resourceArbject.setInitiative(currentPlot.initiative);
				setPlayer(PLAYER, ""+resourceArbject.getInitiative());
				currentCard = null;
			}
		} else if(gameState.equals(DRAW)){
			addCardsToHand(drawCards(2));
			setGameState(WAITING_MARSHALLING);
		} else if(gameState.equals(WAITING_MARSHALLING)){
			//Quick-fix for single player case
//			if(players.size() == 1){
//				gameState = PRODUCTION;
//			}
			if(proceed){
				setGameState(PRODUCTION);
				proceed = false;
			}
		} else if(gameState.equals(PRODUCTION)){
			resourceArbject.addMoney(currentPlot.production);
			setGameState(MARSHALLING);
		} else if(gameState.equals(MARSHALLING)){
			handInterface.setVisible(true);
			proceedButton.setVisible(true);
			if(proceed){
				proceed = false;
				handInterface.setVisible(false);
				proceedButton.setVisible(false);
				currentCard = null;
				if(nextPlayerIndex < players.size()){
					//notify next player to activate
					setSharedData(players.get(nextPlayerIndex), CONTINUE, "" + (nextPlayerIndex + 1));
				} else{
					//notify first player to challenge
					setSharedData(players.get(0), CONTINUE, "1");
				}
				setGameState(WAITING_CHALLENGE);
			}
		} else if(gameState.equals(WAITING_MARSHALLING)){
			
		} else if(gameState.equals(CHALLENGES)){
			proceedButton.setVisible(true);
			redButton.setVisible(true);
			greenButton.setVisible(true);
			blueButton.setVisible(true);
			combatStats.setVisible(true);
			if(currentChallengeType != null){
				redButton.setVisible(false);
				greenButton.setVisible(false);
				blueButton.setVisible(false);
			}
			if(proceed){
				proceed = false;
				currentChallengeType = null;
				challengingUnits.clear();
				proceedButton.setVisible(false);
				redButton.setVisible(false);
				greenButton.setVisible(false);
				blueButton.setVisible(false);
				combatStats.setVisible(false);
				resourceArbject.setMoney(0);
				currentPlot = null;
				if(nextPlayerIndex < players.size()){
					//notify next player to challenge
					setSharedData(players.get(nextPlayerIndex), CONTINUE, "" + (nextPlayerIndex + 1));
				} else{
					//notify all players for next round
					setSharedData(players.get(0), NEXT_ROUND, NEXT_ROUND);
				}
				setGameState(WAITING_CHALLENGE);
			}
		} else if(gameState.equals(WAITING_CHALLENGE)){
			if(currentChallenger != null && currentChallengeType != null){
				setGameState(DEFENDING);
			}
		} else if(gameState.equals(DEFENDING)){
			proceedButton.setVisible(true);
			combatStats.setVisible(true);
			if(proceed){
				proceed = false;
				proceedButton.setVisible(false);
				combatStats.setVisible(false);
				calculateChallenge();
			}
		} else if(gameState.equals(STAND)){
			for(Arbject arbject : arbjects.values()){
				((CardArbject)arbject).setKneel(false);
			}
			setGameState(PLOT);
		}
	}
	
	public void serverUpdate(){
    }
	
	@Override
	protected void onBeamComplete(NfcEvent event){
		if(gameState.equals(CHALLENGES)){
			setGameState(WAITING_DEFENCE);
//			setSharedData(localMAC, currentChallengeType, packChallengers(challengingUnits));
		}
	}

	/**
	 * BEAM EVENT
	 */
	@Override
	protected void beamReceived(String deviceMac){
		if(gameState.equals(SETUP)){
			addPlayer(deviceMac);
			setSharedData(LOCAL_MAC, JOIN, LOCAL_MAC);
		} else if(gameState.equals(WAITING_CHALLENGE)){
			currentChallenger = deviceMac;
			setSharedData(currentChallenger, SEND_CHALLENGERS, "");
		}
	}
	
	/**
	 * TAG EVENT
	 */
	@Override
	protected void nfcReceived(Arbject arbject){
		currentArbject = (CardArbject)arbject;
		if(gameState.equals(SETUP)){
			resourceArbject = currentArbject;
			setCard(resourceArbject, Cards.cards.get(Card.RESOURCES));
		} else if(gameState.equals(MARSHALLING) && currentCard != null){
			if(resourceArbject.getMoney() >= currentCard.cost){
				if((currentCard.hasType(Card.UNIT) || currentCard.hasType(Card.LAND)) &&
						currentArbject.isVacant()){
					resourceArbject.subtractMoney(currentCard.cost);
		        	setCard(currentArbject, currentCard);
		        	drop(currentCard);
				} else if(currentCard.type.equals(Card.EQUIP) && currentArbject.hasType(Card.UNIT)){
					//TODO: equipCard(currentArbject, currentCard);
				}
	        }
		} else if(gameState.equals(CHALLENGES) && currentArbject.hasType(Card.UNIT) &&
				currentChallengeType!= null &&
				!currentArbject.getKneel() && currentArbject.hasColor(currentChallengeType)){
			setSharedData(currentArbject.getID(), KNEEL, ""+true);
			challengingUnits.add(currentArbject.getID());
			combatStats.addAttack(currentArbject.getCard().power);
		} else if(gameState.equals(DEFENDING) && currentArbject.hasType(Card.UNIT) &&
				!currentArbject.getKneel() && currentArbject.hasColor(currentChallengeType)){
			setSharedData(currentArbject.getID(), KNEEL, ""+true);
			defendingUnits.add(currentArbject.getID());
			combatStats.addDefence(currentArbject.getCard().power);
		} else if(gameState.equals(RED_DEFEATED) && currentArbject.hasType(Card.UNIT)
				/*currentArbject.isOwnedBy(localMAC)*/){
			//TODO: should be multiple picks with higher claim
			clearCard(currentArbject);
//			setCard(currentArbject, Cards.cards.get(Card.NONE));
			setGameState(WAITING_CHALLENGE);
			setSharedData(currentChallenger, VICTORY, "");
			currentChallenger = null;
//			currentChallengeType = null;
//			defendingUnits.clear();
		}
	}

	/**
	 * TOUCH EVENT
	 */
	@Override
	public void performTapAction(String action, Object object){
		if(action.equals(CONTINUE)){
			proceed = true;
		} else if(action.equals(RED)){
			redButton.setAvailable(false);
			currentChallengeType = action;
			
		} else if(action.equals(GREEN)){
			greenButton.setAvailable(false);
			currentChallengeType = action;
		} else if(action.equals(BLUE)){
			blueButton.setAvailable(false);
			currentChallengeType = action;
		} else if(gameState.equals(PLOT)){
			currentPlot = plotHand.get(plotHand.indexOf(object));
		} else if(gameState.equals(MARSHALLING)){
			currentCard = localHand.get(localHand.indexOf(object));
		} else if(gameState.equals(GREEN_VICTORY)){
			//TODO: pick opponents card to drop
		}
	}
	
	/**
	 * ACCELEREOMETER
	 */
	@Override
	protected void accelerometerChanged(float ax, float ay, float az){
		//TODO: accelerometer effects
    }
	
	@Override
	public void onConfigurationChanged(Configuration newConfig) {
	    super.onConfigurationChanged(newConfig);

	    // Checks the orientation of the screen
	    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
	        Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
	    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
	        Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
	    }
	}
	
	/**
	 * SERVER EVENT
	 */
	@Override
	public void serverConnectionEventOccurred(String[] msg) {
		if(msg[1].equals(JOIN)){
			//Tell joined player that this device is also in the game
			addPlayer(msg[0]);
		} else if(msg[1].equals(CARD)){
			//Update arbject card content
			if(msg[2].equals(Card.NONE)){
				((CardArbject)arbjects.get(msg[0])).markForReset();
			} else{
				((CardArbject)arbjects.get(msg[0])).setCard(Cards.cards.get(msg[2]));
			}
		} else if(msg[1].equals(KNEEL)){
			if(msg[2].equals("true")){
				((CardArbject)arbjects.get(msg[0])).setKneel(true);
			} else{
				((CardArbject)arbjects.get(msg[0])).setKneel(false);
			}
		} else if(msg[1].equals(PLAYER)){
			//Update player turn order
			if(players.contains(msg[0])){
				initiatives.put(msg[0], Integer.parseInt(msg[2]));
			}
			if(allPlayersPickedPlot()){
				notifyFirstPlayer();
			}
		} else if(msg[1].equals(CONTINUE)){
			nextPlayerIndex = Integer.parseInt(msg[2]);
			if(LOCAL_MAC.equals(msg[0])){
				if(gameState.equals(WAITING_MARSHALLING) || gameState.equals(DRAW)){
					//Local game has been notified to continue for activation
					proceed = true;
//					setGameState(PRODUCTION);
				} else if(gameState.equals(WAITING_CHALLENGE)){
					//Local game has been notified to continue for challenge
					setGameState(CHALLENGES);
				}
			}
		} else if(msg[1].equals(RED) || msg[1].equals(GREEN) || msg[1].equals(BLUE)){
			if(msg[0].equals(currentChallenger)){
				currentChallengeType = msg[1];
				challengingUnits = parseChallengers(msg[2]);
				
				combatStats.addAttack(countAttack(challengingUnits));
//				game state is set to defending in update to avoid dead end in state
			}
		} else if(msg[1].equals(SEND_CHALLENGERS)){
			if(msg[0].equals(LOCAL_MAC)){
				setSharedData(LOCAL_MAC, currentChallengeType, packChallengers(challengingUnits));
			}
		} else if(msg[1].equals(VP) && msg[0].equals(LOCAL_MAC)){
			resourceArbject.addVP(Integer.parseInt(msg[2]));
		} else if(msg[1].equals(VICTORY) && msg[0].equals(LOCAL_MAC)){
			currentChallenger = null;
			currentChallengeType = null;
			challengingUnits.clear();
			combatStats.reset();
			setGameState(CHALLENGES);
		} else if(msg[1].equals(NEXT_ROUND)){
			initNextRound();
			setGameState(STAND);
		}
	}
	
	private void shuffleDeck(ArrayDeque<Card> deck){
		ArrayList<Card> tempDeck = new ArrayList<Card>(deck);
		Collections.shuffle(tempDeck);
//		return tempDeck;
		deck.clear();
//		for(Card card : tempDeck){
//			deck.add(card);
//		}
		deck.addAll(tempDeck);
//		localDeck = new ArrayDeque<Card>(tempDeck);
	}
	
	
	private ArrayList<Card> pollPlotCards(ArrayDeque<Card> deck){
		ArrayList<Card> tempDeck = new ArrayList<Card>();
		while(deck.peekFirst().type.equals(Card.PLOT)){
			tempDeck.add(deck.pollFirst());
		}
		return tempDeck;
	}
	
	
	private ArrayList<Card> drawCards(int drawSize){
		ArrayList<Card> drawnCards = new ArrayList<Card>();
		for(int i = 0; i < drawSize; i++){
			if(!localDeck.isEmpty()){
				drawnCards.add(localDeck.pollFirst());
			}
		}
		return drawnCards;
	}
	
	
	private void setHand(ArrayList<Card> newHand){
		try{
			localHand = newHand;
			handInterface.clear();
			for(Card card : localHand){
				handInterface.add(card);
			}
		} catch(Exception e){
			e.printStackTrace();
		}
	}
	
	
	private void setPlot(ArrayList<Card> newHand){
		plotHand = newHand;
		plotInterface.clear();
		for(Card card : plotHand){
			plotInterface.add(card);
		}
	}
	
	
	private void addCardsToHand(ArrayList<Card> newCards){
		localHand.addAll(newCards);
		handInterface.clear();
		for(Card card : localHand){
			handInterface.add(card);
		}
	}
	
	
	private void drop(Card card){
		localHand.remove(card);
		setHand(localHand);
	}
	
	public void addPlayer(String newPlayer){
		if(!players.contains(newPlayer)){
			players.add(newPlayer);
			Collections.sort(players);
			initiatives.put(newPlayer, -1);
		}
	}
	
	public void setPlayer(String var, String value){
		setSharedData(LOCAL_MAC, var, value);
	}
	
	
	private void notifyFirstPlayer(){
		players = sortPlayersAfterInitiative(players, initiatives);
		nextPlayerIndex = 0;
		setSharedData(players.get(nextPlayerIndex), CONTINUE, "" + (nextPlayerIndex + 1));
	}
	
	private ArrayList<String> sortPlayersAfterInitiative(ArrayList<String> ps, HashMap<String, Integer> is){
		int highestInitiative = 0;
		ArrayList<String> sortedPlayers = new ArrayList<String>();
		//TODO: have proper sorting here
		for(String player : ps){
			if(is.get(player) > highestInitiative){
				sortedPlayers.add(0, player);
				highestInitiative = is.get(player);
			} else{
				sortedPlayers.add(player);
			}
		}
		return sortedPlayers;
	}
	
	private boolean allPlayersPickedPlot(){
		for(String player : players){
			if(initiatives.get(player) == -1){
				return false;
			}
		}
		return true;
	}
	
	private void initNextRound(){
		redButton.setAvailable(true);
		greenButton.setAvailable(true);
		blueButton.setAvailable(true);
		resetInitiatives();
		combatStats.reset();
	}
	
	private void resetInitiatives(){
		for(String player : players){
			initiatives.put(player, -1);
		}
	}
	
	public void setCard(CardArbject arbject, Card card){
		arbject.setCard(card);
		setSharedData(arbject.getID(), CARD, card.name);
	}
	
	public void resetCardArbjects(){
		for(Arbject arbject : arbjects.values()){
			clearCard((CardArbject)arbject);
		}
	}
	
	public void kneelCard(CardArbject arbject, boolean kneel){
//		arbject.setKneel(kneel);
		setSharedData(arbject.getID(), KNEEL, ""+kneel);
	}
	
	public void clearCard(CardArbject arbject){
//		arbject.markForReset();
		setSharedData(arbject.getID(), CARD, Card.NONE);
	}
	
	public String packChallengers(ArrayList<String> challengers){
		String pack = new String();
		for(String challenger : challengers){
			pack += challenger + ",";
		}
		return pack;
	}
	
	public ArrayList<String> parseChallengers(String message){
		ArrayList<String> challengers = new ArrayList<String>();
		String[] parse = message.split(",");
		for(int i = 0; i < parse.length; i++){
			if(!parse[i].isEmpty()){
				challengers.add(parse[i]);
			}
		}
		return challengers;
	}
	
	public int countAttack(ArrayList<String> challengers){
		int attack = 0;
		for(String challenger : challengers){
			CardArbject challengerArb = ((CardArbject)getArbject(challenger));
			if(challengerArb.getCard() != null){
				//TODO: fix the actual problem
				attack += challengerArb.getCard().power;
			}
		}
		return attack;
	}
	
	public void calculateChallenge(){
		int attack = 0, defence = 0, unopposed = 0;
		for(String challenger : challengingUnits){
			attack += ((CardArbject)getArbject(challenger)).getCard().power;
		}
		for(String defender : defendingUnits){
			defence += ((CardArbject)getArbject(defender)).getCard().power;
		}
		if(attack >= defence){
			if(defence == 0){
				unopposed = 1;
			}
			//TODO: claim should be considered
			if(currentChallengeType.equals(RED)){
				concludeChallenge(unopposed, RED);
			} else if(currentChallengeType.equals(GREEN)){
				//TODO: the victor picks a card, not random like now
				drop(localHand.get((int)Math.min(Math.random() * localHand.size(),
						 localHand.size() - 1)));
				concludeChallenge(unopposed, GREEN);
			} else if(currentChallengeType.equals(BLUE)){
				concludeChallenge(resourceArbject.pickVP(1) + unopposed, BLUE);
			}
		} else{
			concludeChallenge(0, DEFENDER_WON);
//			setGameState(WAITING_CHALLENGE);
//			setSharedData(currentChallenger, VICTORY, "");
		}
	}
	
	private void concludeChallenge(int vp, String challengeType){
		setSharedData(currentChallenger, VP, "" + vp);
		
		if(challengeType.equals(RED)){
			setGameState(RED_DEFEATED);
		} else{
			setGameState(WAITING_CHALLENGE);
			setSharedData(currentChallenger, VICTORY, "");
			currentChallenger = null;
		}
		
		currentChallengeType = null;
		challengingUnits.clear();
		defendingUnits.clear();
		combatStats.reset();
	}
	
	private void setGameState(String newState){
		gameState = newState;
		debugText.setText(newState);
	}
}
